<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Common;

use Srv\Libs\Tools\Idna;

final class CommFile
{
    public const FILE_UNIT_NUM          = 1024;                                                     // 进制大小
    private static array $sizeUnit      = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];    // 单位名称

    /**
     * @param int $fileSize
     * @param int $decimal
     * @param int $units
     * @return string
     * getSizeStr
     */
    public static function getSizeStr(int $fileSize, int $decimal = 2, int $units = self::FILE_UNIT_NUM):string
    {
        $i = 1;
        $j = count(self::$sizeUnit);
        $decimalPow = pow(10, $decimal);
        while($fileSize >= pow($units, $i) && $i <= $j) ++$i;
        return ceil(($fileSize/pow($units, $i-1))*$decimalPow)/$decimalPow.' '.self::$sizeUnit[$i-1];
    }

    /**
     * @param string $url
     * @return string
     * urlToLinuxPath
     */
    public static function urlToLinuxPath(string $url):string
    {
        return preg_replace_callback("/[\/\\\\]+/", function(){return '/';}, $url);
    }

    /**
     * @param string $filePath
     * @return bool
     * isExistsFile
     */
    public static function isExistsFile(string $filePath):bool
    {
        if(preg_match("/^phar:\/\/(.*)$/i", $filePath, $match)){
            if(preg_match("/^(\S+\.phar)(.*)/i", $match[1], $matchList)) return file_exists($matchList[1]);
            return false;
        }else{
            return file_exists($filePath);
        }
    }

    /**
     * @param string $folderPath
     * @return bool
     * isExistsFolder
     */
    public static function isExistsFolder(string $folderPath):bool
    {
        if(preg_match("/^phar:\/\/(.*)$/i", $folderPath, $match)){
            if(preg_match("/^(\S+\.phar)(.*)/i", $match[1], $matchList)) return file_exists($matchList[1]);
            return false;
        }else{
            return (is_dir($folderPath) && file_exists($folderPath));
        }
    }

    /**
     * @param string $desPath
     * @param string $srcPath
     * @return string
     * getRelativePath
     */
    public static function getRelativePath(string $desPath, string $srcPath):string
    {
        $srcPath    = trim(self::urlToLinuxPath($srcPath));
        if(CommString::isEmpty($srcPath)) return '';
        $desPath    = trim(self::urlToLinuxPath($desPath));
        if(CommString::isEmpty($desPath)) return '';
        $isRelative = false;
        $srcPathFirstChar   = substr($srcPath, 0, 1);
        $desPathFirstChar   = substr($desPath, 0, 1);
        if($srcPathFirstChar === '/' && $srcPathFirstChar === $desPathFirstChar) $isRelative = true;
        $srcArray   = explode('/', dirname($srcPath));
        $desArray   = explode('/', $desPath);
        if(!$isRelative && $srcArray[0] == $desArray[0]) $isRelative = true;
        if(!$isRelative) return $desPath;
        $isFound    = false;
        $srcRes     = '';
        foreach($srcArray as $key => $val){
            if(!$isFound && (!isset($desArray[$key]) || $desArray[$key] != $val)) $isFound = true;
            if($isFound){
                $srcRes .= '../';
            }else{
                unset($desArray[$key]);
            }
        }
        return $srcRes.implode('/', $desArray);
    }

    /**
     * @param string $filePath
     * @param string $content
     * @param int $sizeNum
     * @param int $sizeStart
     * @return bool
     * getContent
     */
    public static function getContent(string $filePath, string &$content, int $sizeNum = -1, int $sizeStart = 0):bool
    {
        if(!is_file($filePath) || !self::isExistsFile($filePath)) return false;
        if(!($fp = @fopen($filePath, 'r'))) return false;
        if($sizeStart !== 0 && fseek($fp, $sizeStart) !== 0){
            fclose($fp);
            return false;
        }
        if($sizeNum === 0){
            $content    = '';
        }else if($sizeNum > 0){
            $content    = fread($fp, $sizeNum);
        }else{
            $content    = '';
            while($tmpString = fgets($fp)){
                $content .= $tmpString;
                if(feof($fp)) break;
            }
        }
        fclose($fp);
        return true;
    }

    /**
     * @param string $filePath
     * @param string $content
     * @param int $lineNum
     * @param int $lineStart
     * @return bool
     * getContentLine
     */
    public static function getContentLine(string $filePath, string &$content, int $lineNum = -1, int $lineStart = 0):bool
    {
        if(!is_file($filePath) || !self::isExistsFile($filePath)) return false;
        if(!($fp = @fopen($filePath, 'r'))) return false;
        $lineIndex  = 0;
        $lineRead   = 0;
        $content    = '';
        while($tmpString = fgets($fp)){
            if($lineIndex >= $lineStart){
                if($lineNum > 0 && $lineNum <= $lineRead) break;
                $content .= $tmpString;
                $lineRead++;
            }
            $lineIndex++;
            if(feof($fp)) break;
        }
        fclose($fp);
        return true;
    }

    /**
     * @param string $localUrl
     * @param string $content
     * @param string $type
     * @return bool
     * writeFileString
     */
    public static function writeFileString(string $localUrl, string $content, string $type = 'a+'):bool
    {
        $localUrlDir = dirname($localUrl);
        if(!file_exists($localUrlDir)) @mkdir($localUrlDir, 0777, true);
        if(($fp = @fopen($localUrl, $type))){
            fwrite($fp, $content);
            fclose($fp);
            return true;
        }
        return false;
    }

    /**
     * @param array $fileData
     * @param string $dstFile
     * @param array $allowType
     * @param int $maxSize
     * @param string $errType
     * @return bool
     * uploadFile
     */
    public static function uploadFile(array $fileData, string $dstFile, array $allowType = [], int $maxSize = 0, string &$errType = ''):bool
    {
        $errType        = '';
        if(!isset($fileData['tmp_name']) || !isset($fileData['name']) || !isset($fileData['size'])){                // data Error
            $errType    = 'ERR_DATA';
            return false;
        }
        $upSize         = intval($fileData['size']);
        if($upSize < 1 || $upSize > $maxSize){              // File Size Error
            $errType    = 'ERR_SIZE';
            return false;
        }
        $upName         = trim($fileData['name']);
        $upTmpName      = trim($fileData['tmp_name']);
        if(strlen($upTmpName) < 3 || strlen($upName) < 1){  // data Error
            $errType    = 'ERR_NAME';
            return false;
        }
        $upNameExt      = strtolower(substr($upName, strrpos($upName, '.')+1));
        if(count($allowType) > 0 && !in_array('*', $allowType, true) && !in_array($upNameExt, $allowType, true)){   // File Type Error
            $errType    = 'ERR_TYPE';
            return false;
        }
        if(self::isExistsFile($dstFile)){                   // File Exists Error
            $errType    = 'ERR_EXISTS';
            return false;
        }
        if(!move_uploaded_file($upTmpName, $dstFile)){      // Data Upload Success
            $errType    = 'ERR_FAIL';
            return false;
        }
        return true;
    }

    /**
     * @param string $filePath
     * @return bool
     * deleteFile
     */
    public static function deleteFile(string $filePath):bool
    {
        if(!self::isExistsFile($filePath)) return true;
        @unlink($filePath);
        if(!self::isExistsFile($filePath)) return true;
        return false;
    }

    /**
     * @param string $dirPath
     * @return bool
     * deleteDir
     */
    public static function deleteDir(string $dirPath):bool
    {
        if(is_dir($dirPath) && ($dirHand = @opendir($dirPath))){
            while($fileName = @readdir($dirHand)){
                if($fileName != '.' && $fileName != '..'){
                    $fullPath = rtrim($dirPath, '/').'/'.$fileName;
                    if(!is_dir($fullPath)){
                        if(!self::deleteFile($fullPath)) return false;
                    }else{
                        if(!self::deleteDir($fullPath)) return false;
                    }
                }
            }
            @closedir($dirHand);
            @rmdir($dirPath);
            return true;
        }
        return false;
    }

    /**
     * @param string $filePath
     * @return string
     * getMime
     */
    public static function getMime(string $filePath):string
    {
        $fileType           = '';
        if(function_exists('mime_content_type')){
            $fileType       = mime_content_type($filePath);
        }else if(function_exists('finfo_open')){
            $fileInfo       = @finfo_open(FILEINFO_MIME);
            $fileType       = @finfo_file($fileInfo, $filePath);
            @finfo_close($fileInfo);
        }
        return $fileType;
    }

    /**
     * @param string $dirPath
     * @param array $fileList
     * @return bool
     * getFileList
     */
    public static function getFileList(string $dirPath, array &$fileList):bool
    {
        if(is_dir($dirPath) && ($dirHand = @opendir($dirPath))){
            while($fileName = @readdir($dirHand)){
                if($fileName != '.' && $fileName != '..'){
                    $fullPath = rtrim($dirPath, '/').'/'.$fileName;
                    if(!is_dir($fullPath)) $fileList[] = $fullPath;
                }
            }
            @closedir($dirHand);
            return true;
        }
        return false;
    }

    /**
     * @param string $dirPath
     * @param array $dirList
     * @return bool
     * getDirList
     */
    public static function getDirList(string $dirPath, array &$dirList):bool
    {
        if(is_dir($dirPath) && ($dirHand = @opendir($dirPath))){
            while($fileName = @readdir($dirHand)){
                if($fileName != '.' && $fileName != '..'){
                    $fullPath = rtrim($dirPath, '/').'/'.$fileName;
                    if(is_dir($fullPath)) $dirList[] = $fullPath;
                }
            }
            @closedir($dirHand);
            return true;
        }
        return false;
    }

    /**
     * @param string $dirPath
     * @param array $allFileList
     * @return bool
     * getAllFileList
     */
    public static function getAllFileList(string $dirPath, array &$allFileList):bool
    {
        if(is_dir($dirPath) && ($dirHand = @opendir($dirPath))){
            while($fileName = @readdir($dirHand)){
                if($fileName != '.' && $fileName != '..'){
                    $fullPath = rtrim($dirPath, '/').'/'.$fileName;
                    if(!is_dir($fullPath)){
                        $allFileList[] = $fullPath;
                    }else{
                        if(!self::getAllFileList($fullPath, $allFileList)) return false;
                    }
                }
            }
            @closedir($dirHand);
            return true;
        }
        return false;
    }

    /**
     * @param string $dirPath
     * @param array $allDirList
     * @return bool
     * getAllDirList
     */
    public static function getAllDirList(string $dirPath, array &$allDirList):bool
    {
        if(is_dir($dirPath) && ($dirHand = @opendir($dirPath))){
            while($fileName = @readdir($dirHand)){
                if($fileName != '.' && $fileName != '..'){
                    $fullPath = rtrim($dirPath, '/').'/'.$fileName;
                    if(is_dir($fullPath)){
                        $allDirList[] = $fullPath;
                        if(!self::getAllDirList($fullPath, $allDirList)) return false;
                    }
                }
            }
            @closedir($dirHand);
            return true;
        }
        return false;
    }

    /**
     * @param string $url
     * @param array $param
     * @param string $contentNet
     * @return bool
     * getContentNet
     */
    public static function getContentNet(string $url, array $param = [], string &$contentNet = ''):bool
    {
        if(!CommValid::isHttps($url) && !CommValid::isHttp($url)) return false;
        $setInfo                            = [];       // 设置初始信息
        $setInfo['IS_NOBODY']               = false;    // 是否不获取内容
        $setInfo['IS_FOLLOWLOCATION']       = true;     // 是否允许重定向
        $setInfo['FOLLOWLOCATION_NUM']      = 5;        // 重定向最大次数
        $setInfo['TIMEOUT']                 = 60;       // 超时
        $setInfo['IP']                      = '';       // 主机IP
        $setInfo['PORT']                    = 0;        // 端口
        $setInfo['METHOD']                  = 'GET';    // GET/POST/HEAD
        $setInfo['BIND_IP']                 = '';       // 绑定服务器的IP
        $setInfo['IS_KEEPALIVE']            = false;    // 是否长连接
        $setInfo['SSL']                     = [];       // SSL参数
        if(count($param) > 0) foreach($param as $key => $val){
            switch($key){
                case 'IS_NOBODY' : {            // 是否获取正文
                    $setInfo['IS_NOBODY']               = !!$val;
                    break;
                }
                case 'IS_FOLLOWLOCATION' : {    // 是否自动重定向
                    $setInfo['IS_FOLLOWLOCATION']       = !!$val;
                    break;
                }
                case 'FOLLOWLOCATION_NUM' : {   // 重定向最大次数
                    $setInfo['FOLLOWLOCATION_NUM']      = min(max(intval($val), 0), 999);
                    break;
                }
                case 'TIMEOUT' : {              // 超时时间秒
                    $setInfo['TIMEOUT']                 = min(max(intval($val), 0), 999);
                    break;
                }
                case 'IP' : {                   // 指定服务器IP
                    $setInfo['IP']                      = trim($val);
                    break;
                }
                case 'PORT' : {                 // 指定服务器端口
                    $setInfo['PORT']                    = max(min(intval($val), 65535), 0);
                    break;
                }
                case 'SSL' : {                  // 指定请求SSL信息
                    $setInfo['SSL']                     = array_merge($setInfo['SSL'], $val);
                    break;
                }
                case 'METHOD' : {               // 请求方式类型
                    $setInfo['METHOD']                  = strtoupper($val);
                    break;
                }
                case 'BIND_IP' : {              // 设置请求使用本机的某个IP
                    $setInfo['BIND_IP']                 = trim($val);
                    break;
                }
                case 'IS_KEEPALIVE' : {         // 是否保持Keepalive状态
                    $setInfo['IS_KEEPALIVE']            = !!$val;
                    break;
                }
                case 'PROXY_HTTP_IP' : {        // HTTP代理服务器IP
                    $setInfo['PROXY_HTTP_IP']           = trim($val);
                    break;
                }
                case 'PROXY_HTTP_PORT' : {      // HTTP代理服务器端口
                    $setInfo['PROXY_HTTP_PORT']         = max(min(intval($val), 65535), 0);
                    break;
                }
                case 'PROXY_HTTP_USER' : {      // HTTP代理服务器USER
                    $setInfo['PROXY_HTTP_USER']         = trim($val);
                    break;
                }
                case 'PROXY_HTTP_PASS' : {      // HTTP代理服务器PASS
                    $setInfo['PROXY_HTTP_PASS']         = trim($val);
                    break;
                }
                case 'PROXY_SOCKS5_IP' : {      // SOCKS5代理服务器IP
                    $setInfo['PROXY_SOCKS5_IP']         = trim($val);
                    break;
                }
                case 'PROXY_SOCKS5_PORT' : {    // SOCKS5代理服务器端口
                    $setInfo['PROXY_SOCKS5_PORT']       = max(min(intval($val), 65535), 0);
                    break;
                }
                case 'PROXY_SOCKS5_USER' : {    // SOCKS5代理服务器USER
                    $setInfo['PROXY_SOCKS5_USER']       = trim($val);
                    break;
                }
                case 'PROXY_SOCKS5_PASS' : {    // SOCKS5代理服务器PASS
                    $setInfo['PROXY_SOCKS5_PASS']       = trim($val);
                    break;
                }
            }
        }
        if(!$setInfo['IS_FOLLOWLOCATION']) $setInfo['FOLLOWLOCATION_NUM'] = 0;  // 不允许重定向
        $setInfo['IS_NOBODY'] && $setInfo['METHOD'] = 'HEAD';
        $toUrl          = '';
        $protocol       = 'http://';
        if(preg_match("/(.*:\/\/)/", $url, $match)){
            $protocol   = strtolower(trim($match[1]));
            $url        = substr($url, strlen($protocol));
        }
        if(preg_match("/([^\/\\\\]+)(.*)/", $url, $match)){
            $hostPort   = trim($match[1]);
            $toUrl      = trim($match[2]);
        }else{
            $hostPort   = $url;
        }
        strlen($toUrl) < 1 && $toUrl = '/';
        $hostPortData       = explode(':', $hostPort);
        $serverName         = Idna::encode($hostPortData[0]??'');
        $serverPort         = intval($hostPortData[1]??0);
        $ip                 = trim($setInfo['IP']);
        $port               = intval($setInfo['PORT']);
        if($port < 1 && $serverPort > 0) $port = $serverPort;
        if(strlen($ip) < 3 || !(CommValid::isIp($ip) || CommValid::isIpv6($ip))){
            $ip             = gethostbyname($serverName);
            if(!(CommValid::isIp($ip) || CommValid::isIpv6($ip))) return false;
        }
        $serverHostStr      = $serverName;
        $isSsl              = false;
        if($protocol === 'http://'){
            $port === 0 && $port = 80;
            $port !== 80 && $serverHostStr .= ':'.$serverPort;
            $serverHostPort = $ip.':'.$port;
        }else if($protocol == 'https://' || $protocol == 'ssl://' || $protocol == 'tls://'){
            $port === 0 && $port = 443;
            $port !== 443 && $serverHostStr .= ':'.$serverPort;
            $serverHostPort = 'ssl://'.$ip.':'.$port;
            $isSsl          = true;
        }else{
            $serverHostPort = $ip.':'.$port;
        }
        if($port < 1 || $port > 65535) return false;
        $socketData                             = [];
        $socketData['ssl']['verify_peer']       = false;
        $socketData['ssl']['verify_peer_name']  = false;
        if(!CommValid::isIp($serverName) && !CommValid::isIpv6($serverName)) $socketData['ssl']['peer_name'] = $serverName; //指定主机名
        count($setInfo['SSL']) > 0 && $socketData['ssl'] = array_merge($socketData['ssl'], $setInfo['SSL']);
        $bindIp                 = trim($setInfo['BIND_IP']);
        if(strlen($bindIp) > 0 && (CommValid::isIp($bindIp) || CommValid::isIpv6($bindIp))) $socketData['socket']['bindto'] = $bindIp;
        $serverTimeOut          = intval($setInfo['TIMEOUT']);
        $br                     = "\r\n";
        $errno                  = 0;
        $errstr                 = '';
        $proxyHttpIp            = trim($setInfo['PROXY_HTTP_IP']??'');
        $proxySocks5Ip          = trim($setInfo['PROXY_SOCKS5_IP']??'');
        if(strlen($proxySocks5Ip) > 0){ // SOCKS5代理模式
            $proxySocks5Port            = intval($setInfo['PROXY_SOCKS5_PORT']??0);
            $proxySocks5User            = trim($setInfo['PROXY_SOCKS5_USER']??'');
            $proxySocks5Pass            = trim($setInfo['PROXY_SOCKS5_PASS']??'');
            if($proxySocks5Port < 1 || $proxySocks5Port > 65535) return false;
            if(!(CommValid::isIp($proxySocks5Ip) || CommValid::isIpv6($proxySocks5Ip))){
                $proxySocks5IpLookUp    = trim(gethostbyname($proxySocks5Ip));
                if(!(CommValid::isIp($proxySocks5IpLookUp) || CommValid::isIpv6($proxySocks5IpLookUp))) return false;
                $proxySocks5Ip          = $proxySocks5IpLookUp;
            }
            $context                    = @stream_context_create($socketData);
            $fpSocket                   = @stream_socket_client('tcp://'.$proxySocks5Ip.':'.$proxySocks5Port, $errno, $errstr, $serverTimeOut, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN|STREAM_CLIENT_CONNECT, $context);
            if(!$fpSocket) return false;
            @stream_set_blocking($fpSocket, true);
            if(strlen($proxySocks5User) > 0){
                @fwrite($fpSocket, "\05\01\02");
                if(fread($fpSocket, 2) !== "\05\02"){
                    @fclose($fpSocket);
                    return false;
                }
                @fwrite($fpSocket, "\01".chr(strlen($proxySocks5User)).$proxySocks5User.chr(strlen($proxySocks5Pass)).$proxySocks5Pass);
                if(fread($fpSocket, 2) !== "\01\00"){
                    @fclose($fpSocket);
                    return false;
                }
            }else{
                @fwrite($fpSocket, "\05\01\00");
                if(fread($fpSocket, 2) !== "\05\00"){
                    @fclose($fpSocket);
                    return false;
                }
            }
            list($hostStr, $portTmp) = explode(':', $serverHostStr.':', 2);
            unset($portTmp);
            if(CommValid::isIp($hostStr)){          // 0x01 IPv4地址,4字节长度
                $socks5Send         = "\05\01\00\01".inet_pton($hostStr).pack('n', $port);
            }else if(CommValid::isIpv6($hostStr)){  // 0x04 IPv6地址,16个字节长度
                $socks5Send         = "\05\01\00\04".inet_pton($hostStr).pack('n', $port);
            }else{                                  // 0x03 域名,第一个字节为域名长度,剩余的内容为域名,没有\0结尾
                $socks5Send         = "\05\01\00\03".chr(strlen($hostStr)).$hostStr.pack('n', $port);
            }
            @fwrite($fpSocket, $socks5Send);
            if(substr(fread($fpSocket, 10), 0, 3) !== "\05\00\00"){
                @fclose($fpSocket);
                return false;
            }
            if($isSsl && !@stream_socket_enable_crypto($fpSocket, true, STREAM_CRYPTO_METHOD_ANY_CLIENT)){  // 支持SSL
                @fclose($fpSocket);
                return false;
            }
        }else if(strlen($proxyHttpIp) > 0){ // HTTP代理模式
            $proxyHttpPort              = intval($setInfo['PROXY_HTTP_PORT'] ?? 0);
            $proxyHttpUser              = trim($setInfo['PROXY_HTTP_USER']??'');
            $proxyHttpPass              = trim($setInfo['PROXY_HTTP_PASS']??'');
            if($proxyHttpPort < 1 || $proxyHttpPort > 65535) return false;
            if(!(CommValid::isIp($proxyHttpIp) || CommValid::isIpv6($proxyHttpIp))){
                $proxyHttpIpLookUp      = trim(gethostbyname($proxyHttpIp));
                if(!(CommValid::isIp($proxyHttpIpLookUp) || CommValid::isIpv6($proxyHttpIpLookUp))) return false;
                $proxyHttpIp            = $proxyHttpIpLookUp;
            }
            $proxyAuth              = strlen($proxyHttpUser) > 0 ? $proxyHttpUser.':'.$proxyHttpPass : '';
            $context                = @stream_context_create($socketData);
            $fpSocket               = @stream_socket_client('tcp://'.$proxyHttpIp.':'.$proxyHttpPort, $errno, $errstr, $serverTimeOut, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN|STREAM_CLIENT_CONNECT, $context);
            if(!$fpSocket) return false;
            @stream_set_blocking($fpSocket, true);
            $proxyHeads             = 'CONNECT '.$serverHostStr.' HTTP/'.($param['VERSION']??'1.1').$br;
            if(strlen($proxyAuth) > 0) $proxyHeads .= 'Proxy-Authorization: Basic '.base64_encode($proxyAuth).$br;
            $proxyHeads             .= $br;
            @fwrite($fpSocket, $proxyHeads);
            $proxyRsp               = fread($fpSocket, 1024);
            if(!preg_match('/^HTTP\/\d\.\d 200/', $proxyRsp)){
                @fclose($fpSocket);
                return false;
            }
            if($isSsl && !@stream_socket_enable_crypto($fpSocket, true, STREAM_CRYPTO_METHOD_ANY_CLIENT)){   // 支持SSL
                @fclose($fpSocket);
                return false;
            }
        }else{
            $context                = @stream_context_create($socketData);
            $fpSocket               = @stream_socket_client($serverHostPort, $errno, $errstr, $serverTimeOut, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN|STREAM_CLIENT_CONNECT, $context); // ssl://ip:port or ip:port or ip
            if(!$fpSocket) return false;
            @stream_set_blocking($fpSocket, true);
        }
        $heads                      = $setInfo['METHOD'].' '.$toUrl.' HTTP/'.($param['VERSION'] ?? '1.1').$br;
        $heads                      .= 'Host: '.$serverHostStr.$br;
        $headerData                 = $param['HEADER'] ?? [];
        $headerList                 = [];
        if(is_string($headerData)){
            $headerData     = trim($headerData);
            if(strlen($headerData) > 0){
                $heads      .= $headerData.$br;
                foreach(explode("\n", $headerData) as $line){
                    $line   = trim($line);
                    if(strlen($line) < 3) continue;
                    $pos    = strpos($line, ':');
                    if($pos !== false) $headerList[strtolower(substr($line, 0, $pos))][] = substr($line, $pos+1);
                }
            }
        }else if(is_array($headerData) && count($headerData) > 0){
            foreach($headerData as $name => $value){
                if(!is_string($value)) continue;
                if(is_numeric($name)){
                    $value          = trim($value);
                    if(strlen($value) > 0){
                        $heads      .= $value.$br;
                        foreach(explode("\n", $value) as $line){
                            $line   = trim($line);
                            if(strlen($line) < 3) continue;
                            $pos    = strpos($line, ':');
                            if($pos !== false) $headerList[strtolower(substr($line, 0, $pos))][] = substr($line, $pos+1);
                        }
                    }
                }else if(is_string($name)){
                    $name           = trim($name);
                    if(strlen($name) < 1) continue;
                    $value          = trim($value);
                    $headerList[strtolower($name)][] = $value;
                    $heads          .= $name.': '.$value.$br;
                }
            }
        }
        $headTpl        = 'Connection: '.($setInfo['IS_KEEPALIVE'] ? 'keep-alive' : 'close').$br;
        $headTpl        .= 'Cache-Control: max-age=0'.$br;
        $headTpl        .= 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'.$br;
        $headTpl        .= 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'.$br;
        $headTpl        .= 'Accept-Encoding: gzip, deflate'.$br;
        $headTpl        .= 'Accept-Language: en-US,en;q=0.8,zh;q=0.6'.$br;
        $headTpl        .= 'DNT: 1'.$br;
        foreach(explode("\n", $headTpl) as $line){
            $line           = trim($line);
            if(strlen($line) < 3) continue;
            $pos            = strpos($line, ':');
            if($pos !== false){
                $headerKey  = strtolower(substr($line, 0, $pos));
                if(!isset($headerList[$headerKey])) $heads .= $line.$br;
                $headerList[$headerKey][] = substr($line, $pos+1);
            }
        }
        $isSetContentType   = isset($headerList['content-type']);
        $cookieData         = $param['COOKIE'] ?? '';
        if(is_string($cookieData) && strlen($cookieData) > 0){
            $heads          .= 'Cookie: '.$cookieData.$br;
        }else if(is_array($cookieData) && count($cookieData) > 0){
            foreach($cookieData as $name => $value){
                $heads      .= 'Cookie: '.$name.'='.$value.$br;
            }
        }
        $httpAuthUser       = trim($param['USER'] ?? '');
        $httpAuthPass       = trim($param['PASS'] ?? '');
        if(strlen($httpAuthUser) > 0 && strlen($httpAuthPass) > 0) $heads .= 'Authorization: BASIC '.base64_encode($httpAuthUser.':'.$httpAuthPass).$br;
        if($setInfo['METHOD'] === 'POST'){
            $isBoundary     = false;
            $postFile       = $param['POST_FILE'] ?? [];
            $postData       = $param['POST_DATA'] ?? '';
            if(!is_array($postFile)){
                @fclose($fpSocket);
                return false;
            }
            if(count($postFile) > 0 || ($param['POST_TYPE'] ?? '') === 'BOUNDARY') $isBoundary = true;
            if($isBoundary){
                $boundaryFlag       = '----------SRV'.CommString::genRandStr(27);
                $postDataString     = '';
                $postDataArray      = [];
                if(is_string($postData)){
                    if(strlen(trim($postData)) > 0) foreach(explode('&', trim($postData)) as $val){
                        $pos        = strpos($val, '=');
                        if($pos !== false){
                            $postDataArray[substr($val, 0, $pos)] = urldecode(substr($val, $pos+1));
                        }else{
                            $postDataArray[$val] = '';
                        }
                    }
                }else if(is_array($postData)){
                    $postDataArray      = $postData;
                }else{
                    @fclose($fpSocket);
                    return false;
                }
                if(count($postDataArray) > 0) foreach($postDataArray as $name => $val){ // Data
                    $postDataString     .= '--'.$boundaryFlag.$br;
                    $postDataString     .= 'Content-Disposition: form-data; name="'.$name.'"'.$br;
                    $postDataString     .= 'Content-Type: text/plain'.$br;
                    $postDataString     .= $br.$val.$br;
                }
                if(count($postFile) > 0) foreach($postFile as $name => $fileUrl){   // File
                    if(!file_exists($fileUrl)){
                        @fclose($fpSocket);
                        return false;
                    }
                    $fileContent    = '';
                    if(!CommFile::getContent($fileUrl, $fileContent)){
                        @fclose($fpSocket);
                        return false;
                    }
                    $fileType           = CommFile::getMime($fileUrl);
                    $fileName           = basename($fileUrl);
                    $postDataString     .= '--'.$boundaryFlag.$br;
                    $postDataString     .= 'Content-Disposition: form-data; name="'.$name.'"; filename="'.$fileName.'"'.$br;
                    strlen($fileType) > 0 && $postDataString .= 'Content-type: '.$fileType.$br;
                    $postDataString     .= $br.$fileContent.$br;
                }
                if(strlen($postDataString) > 0){
                    $postDataString     .= '--'.$boundaryFlag.'--'.$br;
                    $postDataLength     = strlen($postDataString);
                    $heads              .= 'Content-Length: '.$postDataLength.$br;
                    $heads              .= 'Content-Type: multipart/form-data; boundary='.$boundaryFlag.$br;
                    $heads              .= $br.$postDataString.$br;
                }
            }else{
                $postDataString         = '';
                if(is_string($postData)){
                    $postDataString     = trim($postData);
                }else if(is_array($postData)){
                    foreach($postData as $key => $val) $postDataString .= (strlen($postDataString) > 0 ? '&' : '').$key.'='.rawurlencode(is_string($val) ? $val : @json_encode($val));
                }else{
                    @fclose($fpSocket);
                    return false;
                }
                $postDataLength         = strlen($postDataString);
                if($postDataLength > 0){
                    $heads              .= 'Content-Length: '.$postDataLength.$br;
                    if(!$isSetContentType) $heads .= 'Content-Type: application/x-www-form-urlencoded'.$br;
                    $heads              .= $br.$postDataString.$br;
                }
            }
        }
        $heads                          .= $br;
        @fwrite($fpSocket, $heads);
        $isHead                         = true;
        $isChunked                      = false;
        $isGzip                         = false;
        $headInfoString                 = '';
        $bodyInfoSting                  = '';
        $headToUrl                      = '';
        $nextLength                     = 0;
        $sizeNum                        = 0;
        $remainingNum                   = 0;
        $maxCount                       = 3;
        @stream_set_timeout($fpSocket, $setInfo['TIMEOUT']);
        while(!$content = fgets($fpSocket)){
            if($maxCount < 1){
                @fclose($fpSocket);
                return false;
            }else{
                --$maxCount;
            }
        }
        $headArray                      = explode(' ', trim($content));
        if(count($headArray) > 1){
            $httpCode                   = intval($headArray[1]);
        }else{
            @fclose($fpSocket);
            return false;
        }
        $headInfoString         .= $content;
        while(true){
            if($isHead){
                $content        = fgets($fpSocket);
                if($content === false) break;   // ERROR
                $contentTrim    = trim($content);
                if(strlen($contentTrim) < 1){   // set No Header
                    $isHead     = false;
                    if(strlen($headToUrl) > 0){
                        @fclose($fpSocket);
                        return self::getContentNet($headToUrl, $param, $contentNet);    // 执行重定向
                    }
                    continue;
                }
                $headInfoString .= $content;
                $tmp            = explode(':', $contentTrim);
                $tmpKey         = strtoupper(trim($tmp[0]));
                if($tmpKey === 'CONTENT-LENGTH'){
                    $remainingNum = $sizeNum = intval(trim($tmp[1]));
                    continue;
                }
                if(!$isChunked && $tmpKey === 'TRANSFER-ENCODING' && strpos(strtolower($tmp[1]), 'chunked') !== false) $isChunked = true;        // 分块传输
                if(!$isGzip && $tmpKey === 'CONTENT-ENCODING' && strpos(strtolower($tmp[1]), 'gzip') !== false) $isGzip = true;                  // gzip压缩
                if($tmpKey !== 'Transfer-Encoding'){
                    if($tmpKey === 'LOCATION' && ($httpCode >= 300 && $httpCode < 400) && $setInfo['IS_FOLLOWLOCATION']){  // 重定向 允许
                        if($setInfo['FOLLOWLOCATION_NUM']-- < 1){                       // 超过最大重定向
                            @fclose($fpSocket);
                            return false;
                        }
                        unset($tmp[0]);
                        $tmpVal     = trim(implode(':', $tmp));
                        $headToUrl  = trim(strpos($tmpVal, '://') === false ? $protocol.$hostPort.($tmpVal[0] == '/' ? '' : '/').$tmpVal : $tmpVal);
                    }else{
                        if(strtolower(trim($tmp[0])) == 'set-cookie'){                  // 记录Cookie
                            unset($tmp[0]);
                            $tmpVal = trim(implode(':', $tmp));
                            if(!isset($param['COOKIE'])) $param['COOKIE'] = '';
                            $param['COOKIE'] .= ($param['COOKIE'] == '' ? '' : '; ').substr($tmpVal, 0, intval(strpos($tmpVal, ';')));
                        }
                    }
                }
            }else{
                if($setInfo['IS_NOBODY']) break;
                if($isChunked){                             // 分段传输
                    if($nextLength < 1){
                        $contentNext    = fgets($fpSocket);
                        if($contentNext === false) break;   // ERROR
                        $nextLength     = hexdec(trim($contentNext));
                        if($nextLength < 1) break;          // 结尾
                        $nextLength     += 2;
                        continue;
                    }else{
                        $content        = fread($fpSocket, $nextLength);
                        if($content === false) break;       // ERROR
                        $nextLength     -= strlen($content);
                        if($nextLength <= 0) $content = substr($content, 0, -2);
                        $bodyInfoSting .= $content;
                    }
                }else{
                    $content            = fgets($fpSocket, $sizeNum > 0 ? min(4096, $remainingNum+1) : 4096);
                    if($content === false) break;           // ERROR
                    $bodyInfoSting      .= $content;
                    if(strlen($content) < 1) break;         // 结束
                }
            }
            $remainingNum = $sizeNum - strlen($bodyInfoSting);
            if($sizeNum > 0 && $remainingNum <= 0) break;   // 指定的长度已经读取完成
            $metaData = @stream_get_meta_data($fpSocket);
            if(isset($metaData['timed_out']) && $metaData['timed_out']) break;
        }
        @fclose($fpSocket);
        $fpSocket                   = null;
        unset($headInfoString);
        if($isGzip && strlen($bodyInfoSting) > 0){
            $bodyInfoRes            = @gzdecode($bodyInfoSting);
            if(is_string($bodyInfoRes)) $bodyInfoSting = $bodyInfoRes;
        }
        if($httpCode >= 100 && $httpCode < 500){
            $contentNet             = $bodyInfoSting;
            return true;
        }
        return false;
    }
}